package com.xiao.mall.foundation.service;

import com.xiao.mall.foundation.service.filter.Filter;
import com.xiao.mall.foundation.service.filter.RangeFilter;
import com.xiao.mall.foundation.service.filter.StringFilter;
import org.springframework.data.jpa.domain.Specification;

import javax.persistence.criteria.Expression;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.SetJoin;
import javax.persistence.metamodel.SingularAttribute;
import java.util.function.Function;

/**
 * @author zhoushaofeng
 */
public class SpecificationHelper<E> {
    private Specification<E> specification;
    private final SpecificationBuilder builder;

    public static <E> SpecificationHelper<E> create(Class<E> clazz) {
        return new SpecificationHelper<>();
    }

    public SpecificationHelper() {
        specification = Specification.where(null);
        builder = new SpecificationBuilder();
    }

    public <X> SpecificationHelper<E> buildSpecification(Filter<X> filter, SingularAttribute<? super E, X> field) {
        return buildSpecification(filter, (root) -> root.get(field));
    }

    public SpecificationHelper<E> buildStringSpecification(StringFilter filter, SingularAttribute<? super E, String> field) {
        return buildStringSpecification(filter, (root) -> root.get(field));
    }

    public SpecificationHelper<E> buildStringSpecification(StringFilter filter, Function<Root<E>, Expression<String>> metaClassFunction) {
        Specification<E> otherSpecification = null;
        if (filter != null) {
            otherSpecification = builder.buildSpecification(filter, metaClassFunction);
        }

        if (otherSpecification != null) {
            specification = specification.and(otherSpecification);
        }

        return this;
    }

    public <X> SpecificationHelper<E> buildSpecification(Filter<X> filter, Function<Root<E>, Expression<X>> metaClassFunction) {
        Specification<E> otherSpecification = null;
        if (filter != null) {
            otherSpecification = builder.buildSpecification(filter, metaClassFunction);
        }

        if (otherSpecification != null) {
            specification = specification.and(otherSpecification);
        }

        return this;
    }

    public <X extends Comparable<? super X>> SpecificationHelper<E> buildRangeSpecification(RangeFilter<X> filter, SingularAttribute<? super E, X> field) {
        Specification<E> otherSpecification = null;
        if (filter != null) {
            otherSpecification = builder.buildRangeSpecification(filter, field);
        }

        if (otherSpecification != null) {
            specification = specification.and(otherSpecification);
        }

        return this;    }

    public <X extends Comparable<? super X>> SpecificationHelper<E> buildRangeSpecification(RangeFilter<X> filter, Function<Root<E>, Expression<X>> metaClassFunction) {
        Specification<E> otherSpecification = null;
        if (filter != null) {
            otherSpecification = builder.buildSpecification(filter, metaClassFunction);
        }

        if (otherSpecification != null) {
            specification = specification.and(otherSpecification);
        }

        return this;
    }


    public <X, O> SpecificationHelper<E> buildReferringEntitySpecification(Filter<X> filter,
                                                                           SingularAttribute<? super E, O> reference,
                                                                           SingularAttribute<? super O, X> valueField) {
        Specification<E> otherSpecification = null;
        if (filter != null) {
            otherSpecification = builder.buildReferringEntitySpecification(filter, reference, valueField);
        }

        if (otherSpecification != null) {
            specification = specification.and(otherSpecification);
        }

        return this;
    }

    public <OTHER, MISC, X> SpecificationHelper<E> buildReferringEntitySpecification(Filter<X> filter,
                                                                                            Function<Root<E>, SetJoin<MISC, OTHER>> functionToEntity,
                                                                                            Function<SetJoin<MISC, OTHER>, Expression<X>> entityToColumn) {

        Specification<E> otherSpecification = null;
        if (filter != null) {
            otherSpecification = builder.buildReferringEntitySpecification(filter, functionToEntity, entityToColumn);
        }

        if (otherSpecification != null) {
            specification = specification.and(otherSpecification);
        }

        return this;
    }




    public Specification<E> build() {
        return specification;
    }
}
